/*********************************************************************
*
*      Copyright (C) 2002 Andrew Khan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
***************************************************************************/

package jxl.biff;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;

import jxl.common.Assert;
import jxl.common.Logger;

import jxl.WorkbookSettings;
import jxl.format.Alignment;
import jxl.format.Border;
import jxl.format.BorderLineStyle;
import jxl.format.CellFormat;
import jxl.format.Colour;
import jxl.format.Font;
import jxl.format.Format;
import jxl.format.Orientation;
import jxl.format.Pattern;
import jxl.format.VerticalAlignment;
import jxl.read.biff.Record;

/**
 * Holds an extended formatting record
 */
public class XFRecord extends WritableRecordData implements CellFormat {
	
	/**
	 * The logger
	 */
	private static Logger logger = Logger.getLogger(XFRecord.class);

	/**
	 * The index to the format record
	 */
	public int formatIndex;

	/**
	 * The index of the parent format
	 */
	private int parentFormat;

	/**
	 * The format type
	 */
	private XFType xfFormatType;

	/**
	 * Indicates whether this is a date formatting record
	 */
	private boolean date;

	/**
	 * Indicates whether this is a number formatting record
	 */
	private boolean number;

	/**
	 * The date format for this record. Deduced when the record is read in from
	 * a spreadsheet
	 */
	private DateFormat dateFormat;

	/**
	 * The number format for this record. Deduced when the record is read in
	 * from a spreadsheet
	 */
	private NumberFormat numberFormat;

	/**
	 * The used attribute. Needs to be preserved in order to get accurate
	 * rationalization
	 */
	private byte usedAttributes;
	/**
	 * The index to the font record used by this XF record
	 */
	private int fontIndex;
	/**
	 * Flag to indicate whether this XF record represents a locked cell
	 */
	private boolean locked;
	/**
	 * Flag to indicate whether this XF record is hidden
	 */
	private boolean hidden;
	/**
	 * The alignment for this cell (left, right, centre)
	 */
	private Alignment align;
	/**
	 * The vertical alignment for the cell (top, bottom, centre)
	 */
	private VerticalAlignment valign;
	/**
	 * The orientation of the cell
	 */
	private Orientation orientation;
	/**
	 * Flag to indicates whether the data (normally text) in the cell will be
	 * wrapped around to fit in the cell width
	 */
	private boolean wrap;

	/**
	 * Indentation of the cell text
	 */
	private int indentation;

	/**
	 * Flag to indicate that this format is shrink to fit
	 */
	private boolean shrinkToFit;

	/**
	 * The border indicator for the left of this cell
	 */
	private BorderLineStyle leftBorder;
	/**
	 * The border indicator for the right of the cell
	 */
	private BorderLineStyle rightBorder;
	/**
	 * The border indicator for the top of the cell
	 */
	private BorderLineStyle topBorder;
	/**
	 * The border indicator for the bottom of the cell
	 */
	private BorderLineStyle bottomBorder;

	/**
	 * The border colour for the left of the cell
	 */
	private Colour leftBorderColour;
	/**
	 * The border colour for the right of the cell
	 */
	private Colour rightBorderColour;
	/**
	 * The border colour for the top of the cell
	 */
	private Colour topBorderColour;
	/**
	 * The border colour for the bottom of the cell
	 */
	private Colour bottomBorderColour;

	/**
	 * The background colour
	 */
	private Colour backgroundColour;
	/**
	 * The background pattern
	 */
	private Pattern pattern;
	/**
	 * The options mask which is used to store the processed cell options (such
	 * as alignment, borders etc)
	 */
	private int options;
	/**
	 * The index of this XF record within the workbook
	 */
	private int xfIndex;
	/**
	 * The font object for this XF record
	 */
	private FontRecord font;
	/**
	 * The format object for this XF record. This is used when creating a
	 * writable record
	 */
	private DisplayFormat format;
	/**
	 * Flag to indicate whether this XF record has been initialized
	 */
	private boolean initialized;

	/**
	 * Indicates whether this cell was constructed by an API or read from an
	 * existing Excel file
	 */
	private boolean read;

	/**
	 * The excel format for this record. This is used to display the actual
	 * excel format string back to the user (eg. when generating certain types
	 * of XML document) as opposed to the java equivalent
	 */
	private Format excelFormat;

	/**
	 * Flag to indicate whether the format information has been initialized.
	 * This is false if the xf record has been read in, but true if it has been
	 * written
	 */
	private boolean formatInfoInitialized;

	/**
	 * Flag to indicate whether this cell was copied. If it was copied, then it
	 * can be set to uninitialized, allowing us to change certain format
	 * information
	 */
	private boolean copied;

	/**
	 * A handle to the formatting records. The purpose of this is to read the
	 * formatting information back, for the purposes of output eg. to some form
	 * of XML
	 */
	private FormattingRecords formattingRecords;

	/**
	 * Constants for the used attributes
	 */
	private static final int USE_FONT = 0x4;
	private static final int USE_FORMAT = 0x8;
	private static final int USE_ALIGNMENT = 0x10;
	private static final int USE_BORDER = 0x20;
	private static final int USE_BACKGROUND = 0x40;
	private static final int USE_PROTECTION = 0x80;
	private static final int USE_DEFAULT_VALUE = 0xf8;

	/**
	 * The list of built in date format values
	 */
	private static final int[] dateFormats = new int[] { 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x2d, 0x2e, 0x2f };

	/**
	 * The list of java date format equivalents
	 */
	private static final DateFormat[] javaDateFormats = new DateFormat[] { SimpleDateFormat.getDateInstance(DateFormat.SHORT), SimpleDateFormat.getDateInstance(DateFormat.MEDIUM),
			new SimpleDateFormat("d-MMM"), new SimpleDateFormat("MMM-yy"), new SimpleDateFormat("h:mm a"), new SimpleDateFormat("h:mm:ss a"), new SimpleDateFormat("H:mm"),
			new SimpleDateFormat("H:mm:ss"), new SimpleDateFormat("M/d/yy H:mm"), new SimpleDateFormat("mm:ss"), new SimpleDateFormat("H:mm:ss"), new SimpleDateFormat("mm:ss.S") };

	/**
	 * The list of built in number format values
	 */
	private static int[] numberFormats = new int[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x30 };

	/**
	 * The list of java number format equivalents
	 */
	private static NumberFormat[] javaNumberFormats = new NumberFormat[] { new DecimalFormat("0"), new DecimalFormat("0.00"), new DecimalFormat("#,##0"), new DecimalFormat("#,##0.00"),
			new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("0%"),
			new DecimalFormat("0.00%"), new DecimalFormat("0.00E00"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("#,##0.00;(#,##0.00)"),
			new DecimalFormat("#,##0.00;(#,##0.00)"), new DecimalFormat("#,##0;(#,##0)"), new DecimalFormat("$#,##0;($#,##0)"), new DecimalFormat("#,##0.00;(#,##0.00)"),
			new DecimalFormat("$#,##0.00;($#,##0.00)"), new DecimalFormat("##0.0E0") };

	// Type to distinguish between biff7 and biff8
	private static class BiffType {
	};

	public static final BiffType biff8 = new BiffType();
	public static final BiffType biff7 = new BiffType();

	/**
	 * The biff type
	 */
	private BiffType biffType;

	// Type to distinguish between cell and style records
	private static class XFType {
	}

	protected static final XFType cell = new XFType();
	protected static final XFType style = new XFType();

	/**
	 * Constructs this object from the raw data
	 *
	 * @param t
	 *            the raw data
	 * @param bt
	 *            the biff type
	 */
	public XFRecord(Record t, WorkbookSettings ws, BiffType bt) {
		super(t);

		biffType = bt;

		byte[] data = getRecord().getData();

		fontIndex = IntegerHelper.getInt(data[0], data[1]);
		formatIndex = IntegerHelper.getInt(data[2], data[3]);
		date = false;
		number = false;

		// Compare against the date formats
		for (int i = 0; i < dateFormats.length && date == false; i++) {
			if (formatIndex == dateFormats[i]) {
				date = true;
				dateFormat = javaDateFormats[i];
			}
		}

		// Compare against the number formats
		for (int i = 0; i < numberFormats.length && number == false; i++) {
			if (formatIndex == numberFormats[i]) {
				number = true;
				DecimalFormat df = (DecimalFormat) javaNumberFormats[i].clone();
				DecimalFormatSymbols symbols = new DecimalFormatSymbols(ws.getLocale());
				df.setDecimalFormatSymbols(symbols);
				numberFormat = df;
				// numberFormat = javaNumberFormats[i];
			}
		}

		// Initialize the parent format and the type
		int cellAttributes = IntegerHelper.getInt(data[4], data[5]);
		parentFormat = (cellAttributes & 0xfff0) >> 4;

		int formatType = cellAttributes & 0x4;
		xfFormatType = formatType == 0 ? cell : style;
		locked = ((cellAttributes & 0x1) != 0);
		hidden = ((cellAttributes & 0x2) != 0);

		if (xfFormatType == cell && (parentFormat & 0xfff) == 0xfff) {
			// Something is screwy with the parent format - set to zero
			parentFormat = 0;
			logger.warn("Invalid parent format found - ignoring");
		}

		initialized = false;
		read = true;
		formatInfoInitialized = false;
		copied = false;
	}

	/**
	 * A constructor used when creating a writable record
	 *
	 * @param fnt
	 *            the font
	 * @param form
	 *            the format
	 */
	public XFRecord(FontRecord fnt, DisplayFormat form) {
		super(Type.XF);

		initialized = false;
		locked = true;
		hidden = false;
		align = Alignment.GENERAL;
		valign = VerticalAlignment.BOTTOM;
		orientation = Orientation.HORIZONTAL;
		wrap = false;
		leftBorder = BorderLineStyle.NONE;
		rightBorder = BorderLineStyle.NONE;
		topBorder = BorderLineStyle.NONE;
		bottomBorder = BorderLineStyle.NONE;
		leftBorderColour = Colour.AUTOMATIC;
		rightBorderColour = Colour.AUTOMATIC;
		topBorderColour = Colour.AUTOMATIC;
		bottomBorderColour = Colour.AUTOMATIC;
		pattern = Pattern.NONE;
		backgroundColour = Colour.DEFAULT_BACKGROUND;
		indentation = 0;
		shrinkToFit = false;
		usedAttributes = (byte) (USE_FONT | USE_FORMAT | USE_BACKGROUND | USE_ALIGNMENT | USE_BORDER);

		// This will be set by the initialize method and the subclass
		// respectively
		parentFormat = 0;
		xfFormatType = null;

		font = fnt;
		format = form;
		biffType = biff8;
		read = false;
		copied = false;
		formatInfoInitialized = true;

		Assert.verify(font != null);
		Assert.verify(format != null);
	}

	/**
	 * Copy constructor. Used for copying writable formats, typically when
	 * duplicating formats to handle merged cells
	 *
	 * @param fmt
	 *            XFRecord
	 */
	protected XFRecord(XFRecord fmt) {
		super(Type.XF);

		initialized = false;
		locked = fmt.locked;
		hidden = fmt.hidden;
		align = fmt.align;
		valign = fmt.valign;
		orientation = fmt.orientation;
		wrap = fmt.wrap;
		leftBorder = fmt.leftBorder;
		rightBorder = fmt.rightBorder;
		topBorder = fmt.topBorder;
		bottomBorder = fmt.bottomBorder;
		leftBorderColour = fmt.leftBorderColour;
		rightBorderColour = fmt.rightBorderColour;
		topBorderColour = fmt.topBorderColour;
		bottomBorderColour = fmt.bottomBorderColour;
		pattern = fmt.pattern;
		xfFormatType = fmt.xfFormatType;
		indentation = fmt.indentation;
		shrinkToFit = fmt.shrinkToFit;
		parentFormat = fmt.parentFormat;
		backgroundColour = fmt.backgroundColour;

		// Shallow copy is sufficient for these purposes
		font = fmt.font;
		format = fmt.format;

		fontIndex = fmt.fontIndex;
		formatIndex = fmt.formatIndex;

		formatInfoInitialized = fmt.formatInfoInitialized;

		biffType = biff8;
		read = false;
		copied = true;
	}

	/**
	 * A public copy constructor which can be used for copy formats between
	 * different sheets. Unlike the the other copy constructor, this version
	 * does a deep copy
	 *
	 * @param cellFormat
	 *            the format to copy
	 */
	protected XFRecord(CellFormat cellFormat) {
		super(Type.XF);

		Assert.verify(cellFormat != null);
		Assert.verify(cellFormat instanceof XFRecord);
		XFRecord fmt = (XFRecord) cellFormat;

		if (!fmt.formatInfoInitialized) {
			fmt.initializeFormatInformation();
		}

		locked = fmt.locked;
		hidden = fmt.hidden;
		align = fmt.align;
		valign = fmt.valign;
		orientation = fmt.orientation;
		wrap = fmt.wrap;
		leftBorder = fmt.leftBorder;
		rightBorder = fmt.rightBorder;
		topBorder = fmt.topBorder;
		bottomBorder = fmt.bottomBorder;
		leftBorderColour = fmt.leftBorderColour;
		rightBorderColour = fmt.rightBorderColour;
		topBorderColour = fmt.topBorderColour;
		bottomBorderColour = fmt.bottomBorderColour;
		pattern = fmt.pattern;
		xfFormatType = fmt.xfFormatType;
		parentFormat = fmt.parentFormat;
		indentation = fmt.indentation;
		shrinkToFit = fmt.shrinkToFit;
		backgroundColour = fmt.backgroundColour;

		// Deep copy of the font
		font = new FontRecord(fmt.getFont());

		// Copy the format
		if (fmt.getFormat() == null) {
			// format is writable
			if (fmt.format.isBuiltIn()) {
				format = fmt.format;
			} else {
				// Format is not built in, so do a deep copy
				format = new FormatRecord((FormatRecord) fmt.format);
			}
		} else if (fmt.getFormat() instanceof BuiltInFormat) {
			// read excel format is built in
			excelFormat = (BuiltInFormat) fmt.excelFormat;
			format = (BuiltInFormat) fmt.excelFormat;
		} else {
			// read excel format is user defined
			Assert.verify(fmt.formatInfoInitialized);

			// in this case FormattingRecords should initialize the excelFormat
			// field with an instance of FormatRecord
			Assert.verify(fmt.excelFormat instanceof FormatRecord);

			// Format is not built in, so do a deep copy
			FormatRecord fr = new FormatRecord((FormatRecord) fmt.excelFormat);

			// Set both format fields to be the same object, since
			// FormatRecord implements all the necessary interfaces
			excelFormat = fr;
			format = fr;
		}

		biffType = biff8;

		// The format info should be all OK by virtue of the deep copy
		formatInfoInitialized = true;

		// This format was not read in
		read = false;

		// Treat this as a new cell record, so set the copied flag to false
		copied = false;

		// The font or format indexes need to be set, so set initialized to
		// false
		initialized = false;
	}

	/**
	 * Gets the java date format for this format record
	 *
	 * @return returns the date format
	 */
	public DateFormat getDateFormat() {
		return dateFormat;
	}

	/**
	 * Gets the java number format for this format record
	 *
	 * @return returns the number format
	 */
	public NumberFormat getNumberFormat() {
		return numberFormat;
	}

	/**
	 * Gets the lookup number of the format record
	 *
	 * @return returns the lookup number of the format record
	 */
	public int getFormatRecord() {
		return formatIndex;
	}

	/**
	 * Sees if this format is a date format
	 *
	 * @return TRUE if this refers to a built in date format
	 */
	public boolean isDate() {
		return date;
	}

	/**
	 * Sees if this format is a number format
	 *
	 * @return TRUE if this refers to a built in date format
	 */
	public boolean isNumber() {
		return number;
	}

	/**
	 * Converts the various fields into binary data. If this object has been
	 * read from an Excel file rather than being requested by a user (ie. if the
	 * read flag is TRUE) then no processing takes place and the raw data is
	 * simply returned.
	 *
	 * @return the raw data for writing
	 */
	public byte[] getData() {
		// Format rationalization process means that we always want to
		// regenerate the format info - even if the spreadsheet was
		// read in
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		byte[] data = new byte[20];

		IntegerHelper.getTwoBytes(fontIndex, data, 0);
		IntegerHelper.getTwoBytes(formatIndex, data, 2);

		// Do the cell attributes
		int cellAttributes = 0;

		if (getLocked()) {
			cellAttributes |= 0x01;
		}

		if (getHidden()) {
			cellAttributes |= 0x02;
		}

		if (xfFormatType == style) {
			cellAttributes |= 0x04;
			parentFormat = 0xffff;
		}

		cellAttributes |= (parentFormat << 4);

		IntegerHelper.getTwoBytes(cellAttributes, data, 4);

		int alignMask = align.getValue();

		if (wrap) {
			alignMask |= 0x08;
		}

		alignMask |= (valign.getValue() << 4);

		alignMask |= (orientation.getValue() << 8);

		IntegerHelper.getTwoBytes(alignMask, data, 6);

		data[9] = (byte) 0x10;

		// Set the borders
		int borderMask = leftBorder.getValue();
		borderMask |= (rightBorder.getValue() << 4);
		borderMask |= (topBorder.getValue() << 8);
		borderMask |= (bottomBorder.getValue() << 12);

		IntegerHelper.getTwoBytes(borderMask, data, 10);

		// Set the border palette information if border mask is non zero
		// Hard code the colours to be black
		if (borderMask != 0) {
			byte lc = (byte) leftBorderColour.getValue();
			byte rc = (byte) rightBorderColour.getValue();
			byte tc = (byte) topBorderColour.getValue();
			byte bc = (byte) bottomBorderColour.getValue();

			int sideColourMask = (lc & 0x7f) | ((rc & 0x7f) << 7);
			int topColourMask = (tc & 0x7f) | ((bc & 0x7f) << 7);

			IntegerHelper.getTwoBytes(sideColourMask, data, 12);
			IntegerHelper.getTwoBytes(topColourMask, data, 14);
		}

		// Set the background pattern
		int patternVal = pattern.getValue() << 10;
		IntegerHelper.getTwoBytes(patternVal, data, 16);

		// Set the colour palette
		int colourPaletteMask = backgroundColour.getValue();
		colourPaletteMask |= (0x40 << 7);
		IntegerHelper.getTwoBytes(colourPaletteMask, data, 18);

		// Set the cell options
		options |= indentation & 0x0f;

		if (shrinkToFit) {
			options |= 0x10;
		} else {
			options &= 0xef;
		}

		data[8] = (byte) options;

		if (biffType == biff8) {
			data[9] = (byte) usedAttributes;
		}

		return data;
	}

	/**
	 * Accessor for the locked flag
	 *
	 * @return TRUE if this XF record locks cells, FALSE otherwise
	 */
	protected final boolean getLocked() {
		return locked;
	}

	/**
	 * Accessor for the hidden flag
	 *
	 * @return TRUE if this XF record hides the cell, FALSE otherwise
	 */
	protected final boolean getHidden() {
		return hidden;
	}

	/**
	 * Sets whether or not this XF record locks the cell
	 *
	 * @param l
	 *            the locked flag
	 */
	protected final void setXFLocked(boolean l) {
		locked = l;
		usedAttributes |= USE_PROTECTION;
	}

	/**
	 * Sets the cell options
	 *
	 * @param opt
	 *            the cell options
	 */
	protected final void setXFCellOptions(int opt) {
		options |= opt;
	}

	/**
	 * Sets the horizontal alignment for the data in this cell. This method
	 * should only be called from its writable subclass CellXFRecord
	 *
	 * @param a
	 *            the alignment
	 */
	protected void setXFAlignment(Alignment a) {
		Assert.verify(!initialized);
		align = a;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Sets the indentation
	 *
	 * @param i
	 *            the indentation
	 */
	protected void setXFIndentation(int i) {
		Assert.verify(!initialized);
		indentation = i;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Sets the shrink to fit flag
	 *
	 * @param s
	 *            the shrink to fit flag
	 */
	protected void setXFShrinkToFit(boolean s) {
		Assert.verify(!initialized);
		shrinkToFit = s;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Gets the horizontal cell alignment
	 *
	 * @return the alignment
	 */
	public Alignment getAlignment() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return align;
	}

	/**
	 * Gets the indentation
	 *
	 * @return the indentation
	 */
	public int getIndentation() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return indentation;
	}

	/**
	 * Gets the shrink to fit flag
	 *
	 * @return TRUE if this format is shrink to fit, FALSE otherise
	 */
	public boolean isShrinkToFit() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return shrinkToFit;
	}

	/**
	 * Accessor for whether a particular cell is locked
	 *
	 * @return TRUE if this cell is locked, FALSE otherwise
	 */
	public boolean isLocked() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return locked;
	}

	/**
	 * Gets the vertical cell alignment
	 *
	 * @return the alignment
	 */
	public VerticalAlignment getVerticalAlignment() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return valign;
	}

	/**
	 * Gets the orientation
	 *
	 * @return the orientation
	 */
	public Orientation getOrientation() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return orientation;
	}

	/**
	 * Sets the horizontal alignment for the data in this cell. This method
	 * should only be called from its writable subclass CellXFRecord
	 *
	 * @param c
	 *            the background colour
	 * @param p
	 *            the background pattern
	 */
	protected void setXFBackground(Colour c, Pattern p) {
		Assert.verify(!initialized);
		backgroundColour = c;
		pattern = p;
		usedAttributes |= USE_BACKGROUND;
	}

	/**
	 * Gets the background colour used by this cell
	 *
	 * @return the foreground colour
	 */
	public Colour getBackgroundColour() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return backgroundColour;
	}

	/**
	 * Gets the pattern used by this cell format
	 *
	 * @return the background pattern
	 */
	public Pattern getPattern() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return pattern;
	}

	/**
	 * Sets the vertical alignment for the data in this cell This method should
	 * only be called from its writable subclass CellXFRecord
	 *
	 * 
	 * @param va
	 *            the vertical alignment
	 */
	protected void setXFVerticalAlignment(VerticalAlignment va) {
		Assert.verify(!initialized);
		valign = va;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Sets the vertical alignment for the data in this cell This method should
	 * only be called from its writable subclass CellXFRecord
	 *
	 * 
	 * @param o
	 *            the orientation
	 */
	protected void setXFOrientation(Orientation o) {
		Assert.verify(!initialized);
		orientation = o;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Sets whether the data in this cell is wrapped This method should only be
	 * called from its writable subclass CellXFRecord
	 *
	 * @param w
	 *            the wrap flag
	 */
	protected void setXFWrap(boolean w) {
		Assert.verify(!initialized);
		wrap = w;
		usedAttributes |= USE_ALIGNMENT;
	}

	/**
	 * Gets whether or not the contents of this cell are wrapped
	 *
	 * @return TRUE if this cell's contents are wrapped, FALSE otherwise
	 */
	public boolean getWrap() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		return wrap;
	}

	/**
	 * Sets the border for this cell This method should only be called from its
	 * writable subclass CellXFRecord
	 *
	 * @param b
	 *            the border
	 * @param ls
	 *            the border line style
	 */
	protected void setXFBorder(Border b, BorderLineStyle ls, Colour c) {
		Assert.verify(!initialized);

		if (c == Colour.BLACK || c == Colour.UNKNOWN) {
			c = Colour.PALETTE_BLACK;
		}

		if (b == Border.LEFT) {
			leftBorder = ls;
			leftBorderColour = c;
		} else if (b == Border.RIGHT) {
			rightBorder = ls;
			rightBorderColour = c;
		} else if (b == Border.TOP) {
			topBorder = ls;
			topBorderColour = c;
		} else if (b == Border.BOTTOM) {
			bottomBorder = ls;
			bottomBorderColour = c;
		}

		usedAttributes |= USE_BORDER;

		return;
	}

	/**
	 * Gets the line style for the given cell border If a border type of ALL or
	 * NONE is specified, then a line style of NONE is returned
	 *
	 * @param border
	 *            the cell border we are interested in
	 * @return the line style of the specified border
	 */
	public BorderLineStyle getBorder(Border border) {
		return getBorderLine(border);
	}

	/**
	 * Gets the line style for the given cell border If a border type of ALL or
	 * NONE is specified, then a line style of NONE is returned
	 *
	 * @param border
	 *            the cell border we are interested in
	 * @return the line style of the specified border
	 */
	public BorderLineStyle getBorderLine(Border border) {
		// Don't bother with the short cut records
		if (border == Border.NONE || border == Border.ALL) {
			return BorderLineStyle.NONE;
		}

		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		if (border == Border.LEFT) {
			return leftBorder;
		} else if (border == Border.RIGHT) {
			return rightBorder;
		} else if (border == Border.TOP) {
			return topBorder;
		} else if (border == Border.BOTTOM) {
			return bottomBorder;
		}

		return BorderLineStyle.NONE;
	}

	/**
	 * Gets the line style for the given cell border If a border type of ALL or
	 * NONE is specified, then a line style of NONE is returned
	 *
	 * @param border
	 *            the cell border we are interested in
	 * @return the line style of the specified border
	 */
	public Colour getBorderColour(Border border) {
		// Don't bother with the short cut records
		if (border == Border.NONE || border == Border.ALL) {
			return Colour.PALETTE_BLACK;
		}

		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		if (border == Border.LEFT) {
			return leftBorderColour;
		} else if (border == Border.RIGHT) {
			return rightBorderColour;
		} else if (border == Border.TOP) {
			return topBorderColour;
		} else if (border == Border.BOTTOM) {
			return bottomBorderColour;
		}

		return Colour.BLACK;
	}

	/**
	 * Determines if this cell format has any borders at all. Used to set the
	 * new borders when merging a group of cells
	 *
	 * @return TRUE if this cell has any borders, FALSE otherwise
	 */
	public final boolean hasBorders() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		if (leftBorder == BorderLineStyle.NONE && rightBorder == BorderLineStyle.NONE && topBorder == BorderLineStyle.NONE && bottomBorder == BorderLineStyle.NONE) {
			return false;
		}

		return true;
	}

	/**
	 * If this cell has not been read in from an existing Excel sheet, then
	 * initializes this record with the XF index passed in. Calls initialized on
	 * the font and format record
	 *
	 * @param pos
	 *            the xf index to initialize this record with
	 * @param fr
	 *            the containing formatting records
	 * @param fonts
	 *            the container for the fonts
	 * @exception NumFormatRecordsException
	 */
	public final void initialize(int pos, FormattingRecords fr, Fonts fonts) throws NumFormatRecordsException {
		xfIndex = pos;
		formattingRecords = fr;

		// If this file has been read in or copied,
		// the font and format indexes will
		// already be initialized, so just set the initialized flag and
		// return
		if (read || copied) {
			initialized = true;
			return;
		}

		if (!font.isInitialized()) {
			fonts.addFont(font);
		}

		if (!format.isInitialized()) {
			fr.addFormat(format);
		}

		fontIndex = font.getFontIndex();
		formatIndex = format.getFormatIndex();

		initialized = true;
	}

	/**
	 * Resets the initialize flag. This is called by the constructor of
	 * WritableWorkbookImpl to reset the statically declared fonts
	 */
	public final void uninitialize() {
		// As the default formats are cloned internally, the initialized
		// flag should never be anything other than false
		if (initialized == true) {
			logger.warn("A default format has been initialized");
		}
		initialized = false;
	}

	/**
	 * Sets the XF index. Called when rationalizing the XF records immediately
	 * prior to writing
	 *
	 * @param xfi
	 *            the new xf index
	 */
	final void setXFIndex(int xfi) {
		xfIndex = xfi;
	}

	/**
	 * Accessor for the XF index
	 *
	 * @return the XF index for this cell
	 */
	public final int getXFIndex() {
		return xfIndex;
	}

	/**
	 * Accessor to see if this format is initialized
	 *
	 * @return TRUE if this format is initialized, FALSE otherwise
	 */
	public final boolean isInitialized() {
		return initialized;
	}

	/**
	 * Accessor to see if this format was read in. Used when checking merged
	 * cells
	 *
	 * @return TRUE if this XF record was read in, FALSE if it was generated by
	 *         the user API
	 */
	public final boolean isRead() {
		return read;
	}

	/**
	 * Gets the format used by this format
	 *
	 * @return the format
	 */
	public Format getFormat() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}
		return excelFormat;
	}

	/**
	 * Gets the font used by this format
	 *
	 * @return the font
	 */
	public Font getFont() {
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}
		return font;
	}

	/**
	 * Initializes the internal format information from the data read in
	 */
	private void initializeFormatInformation() {
		// Initialize the cell format string
		if (formatIndex < BuiltInFormat.builtIns.length && BuiltInFormat.builtIns[formatIndex] != null) {
			excelFormat = BuiltInFormat.builtIns[formatIndex];
		} else {
			excelFormat = formattingRecords.getFormatRecord(formatIndex);
		}

		// Initialize the font
		font = formattingRecords.getFonts().getFont(fontIndex);

		// Initialize the cell format data from the binary record
		byte[] data = getRecord().getData();

		// Get the parent record
		int cellAttributes = IntegerHelper.getInt(data[4], data[5]);
		parentFormat = (cellAttributes & 0xfff0) >> 4;
		int formatType = cellAttributes & 0x4;
		xfFormatType = formatType == 0 ? cell : style;
		locked = ((cellAttributes & 0x1) != 0);
		hidden = ((cellAttributes & 0x2) != 0);

		if (xfFormatType == cell && (parentFormat & 0xfff) == 0xfff) {
			// Something is screwy with the parent format - set to zero
			parentFormat = 0;
			logger.warn("Invalid parent format found - ignoring");
		}

		int alignMask = IntegerHelper.getInt(data[6], data[7]);

		// Get the wrap
		if ((alignMask & 0x08) != 0) {
			wrap = true;
		}

		// Get the horizontal alignment
		align = Alignment.getAlignment(alignMask & 0x7);

		// Get the vertical alignment
		valign = VerticalAlignment.getAlignment((alignMask >> 4) & 0x7);

		// Get the orientation
		orientation = Orientation.getOrientation((alignMask >> 8) & 0xff);

		int attr = IntegerHelper.getInt(data[8], data[9]);

		// Get the indentation
		indentation = attr & 0x0F;

		// Get the shrink to fit flag
		shrinkToFit = (attr & 0x10) != 0;

		// Get the used attribute
		if (biffType == biff8) {
			usedAttributes = data[9];
		}

		// Get the borders
		int borderMask = IntegerHelper.getInt(data[10], data[11]);

		leftBorder = BorderLineStyle.getStyle(borderMask & 0x7);
		rightBorder = BorderLineStyle.getStyle((borderMask >> 4) & 0x7);
		topBorder = BorderLineStyle.getStyle((borderMask >> 8) & 0x7);
		bottomBorder = BorderLineStyle.getStyle((borderMask >> 12) & 0x7);

		int borderColourMask = IntegerHelper.getInt(data[12], data[13]);

		leftBorderColour = Colour.getInternalColour(borderColourMask & 0x7f);
		rightBorderColour = Colour.getInternalColour((borderColourMask & 0x3f80) >> 7);

		borderColourMask = IntegerHelper.getInt(data[14], data[15]);
		topBorderColour = Colour.getInternalColour(borderColourMask & 0x7f);
		bottomBorderColour = Colour.getInternalColour((borderColourMask & 0x3f80) >> 7);

		if (biffType == biff8) {
			// Get the background pattern. This is the six most significant bits
			int patternVal = IntegerHelper.getInt(data[16], data[17]);
			patternVal = patternVal & 0xfc00;
			patternVal = patternVal >> 10;
			pattern = Pattern.getPattern(patternVal);

			// Get the background colour
			int colourPaletteMask = IntegerHelper.getInt(data[18], data[19]);
			backgroundColour = Colour.getInternalColour(colourPaletteMask & 0x3f);

			if (backgroundColour == Colour.UNKNOWN || backgroundColour == Colour.DEFAULT_BACKGROUND1) {
				backgroundColour = Colour.DEFAULT_BACKGROUND;
			}
		} else {
			pattern = Pattern.NONE;
			backgroundColour = Colour.DEFAULT_BACKGROUND;
		}

		// Set the lazy initialization flag
		formatInfoInitialized = true;
	}

	/**
	 * Standard hash code implementation
	 * 
	 * @return the hash code
	 */
	public int hashCode() {
		// Must have its formats info initialized in order to compute the hash
		// code
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		int hashValue = 17;
		int oddPrimeNumber = 37;

		// The boolean fields
		hashValue = oddPrimeNumber * hashValue + (hidden ? 1 : 0);
		hashValue = oddPrimeNumber * hashValue + (locked ? 1 : 0);
		hashValue = oddPrimeNumber * hashValue + (wrap ? 1 : 0);
		hashValue = oddPrimeNumber * hashValue + (shrinkToFit ? 1 : 0);

		// The enumerations
		if (xfFormatType == cell) {
			hashValue = oddPrimeNumber * hashValue + 1;
		} else if (xfFormatType == style) {
			hashValue = oddPrimeNumber * hashValue + 2;
		}

		hashValue = oddPrimeNumber * hashValue + (align.getValue() + 1);
		hashValue = oddPrimeNumber * hashValue + (valign.getValue() + 1);
		hashValue = oddPrimeNumber * hashValue + (orientation.getValue());

		hashValue ^= leftBorder.getDescription().hashCode();
		hashValue ^= rightBorder.getDescription().hashCode();
		hashValue ^= topBorder.getDescription().hashCode();
		hashValue ^= bottomBorder.getDescription().hashCode();

		hashValue = oddPrimeNumber * hashValue + (leftBorderColour.getValue());
		hashValue = oddPrimeNumber * hashValue + (rightBorderColour.getValue());
		hashValue = oddPrimeNumber * hashValue + (topBorderColour.getValue());
		hashValue = oddPrimeNumber * hashValue + (bottomBorderColour.getValue());
		hashValue = oddPrimeNumber * hashValue + (backgroundColour.getValue());
		hashValue = oddPrimeNumber * hashValue + (pattern.getValue() + 1);

		// The integer fields
		hashValue = oddPrimeNumber * hashValue + usedAttributes;
		hashValue = oddPrimeNumber * hashValue + parentFormat;
		hashValue = oddPrimeNumber * hashValue + fontIndex;
		hashValue = oddPrimeNumber * hashValue + formatIndex;
		hashValue = oddPrimeNumber * hashValue + indentation;

		return hashValue;
	}

	/**
	 * Equals method. This is called when comparing writable formats in order to
	 * prevent duplicate formats being added to the workbook
	 *
	 * @param o
	 *            object to compare
	 * @return TRUE if the objects are equal, FALSE otherwise
	 */
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}

		if (!(o instanceof XFRecord)) {
			return false;
		}

		XFRecord xfr = (XFRecord) o;

		// Both records must be writable and have their format info initialized
		if (!formatInfoInitialized) {
			initializeFormatInformation();
		}

		if (!xfr.formatInfoInitialized) {
			xfr.initializeFormatInformation();
		}

		if (xfFormatType != xfr.xfFormatType || parentFormat != xfr.parentFormat || locked != xfr.locked || hidden != xfr.hidden || usedAttributes != xfr.usedAttributes) {
			return false;
		}

		if (align != xfr.align || valign != xfr.valign || orientation != xfr.orientation || wrap != xfr.wrap || shrinkToFit != xfr.shrinkToFit || indentation != xfr.indentation) {
			return false;
		}

		if (leftBorder != xfr.leftBorder || rightBorder != xfr.rightBorder || topBorder != xfr.topBorder || bottomBorder != xfr.bottomBorder) {
			return false;
		}

		if (leftBorderColour != xfr.leftBorderColour || rightBorderColour != xfr.rightBorderColour || topBorderColour != xfr.topBorderColour || bottomBorderColour != xfr.bottomBorderColour) {
			return false;
		}

		if (backgroundColour != xfr.backgroundColour || pattern != xfr.pattern) {
			return false;
		}

		if (initialized && xfr.initialized) {
			// Both formats are initialized, so it is sufficient to just do
			// shallow equals on font, format objects,
			// since we are testing for the presence of clones anwyay
			// Use indices rather than objects because of the rationalization
			// process (which does not set the object on an XFRecord)
			if (fontIndex != xfr.fontIndex || formatIndex != xfr.formatIndex) {
				return false;
			}
		} else {
			// Perform a deep compare of fonts and formats
			if (!font.equals(xfr.font) || !format.equals(xfr.format)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Sets the format index. This is called during the rationalization process
	 * when some of the duplicate number formats have been removed
	 * 
	 * @param newindex
	 *            the new format index
	 */
	void setFormatIndex(int newindex) {
		formatIndex = newindex;
	}

	/**
	 * Accessor for the font index. Called by the FormattingRecords objects
	 * during the rationalization process
	 * 
	 * @return the font index
	 */
	public int getFontIndex() {
		return fontIndex;
	}

	/**
	 * Sets the font index. This is called during the rationalization process
	 * when some of the duplicate fonts have been removed
	 * 
	 * @param newindex
	 *            the new index
	 */
	void setFontIndex(int newindex) {
		fontIndex = newindex;
	}

	/**
	 * Sets the format type and parent format from the writable subclass
	 * 
	 * @param t
	 *            the xf type
	 * @param pf
	 *            the parent format
	 */
	protected void setXFDetails(XFType t, int pf) {
		xfFormatType = t;
		parentFormat = pf;
	}

	/**
	 * Changes the appropriate indexes during the rationalization process
	 * 
	 * @param xfMapping
	 *            the xf index re-mappings
	 */
	void rationalize(IndexMapping xfMapping) {
		xfIndex = xfMapping.getNewIndex(xfIndex);

		if (xfFormatType == cell) {
			parentFormat = xfMapping.getNewIndex(parentFormat);
		}
	}

	/**
	 * Sets the font object with a workbook specific clone. Called from the
	 * CellValue object when the font has been identified as a statically shared
	 * font Also called to superimpose a HyperlinkFont on an existing label cell
	 */
	public void setFont(FontRecord f) {
		// This style cannot be initialized, otherwise it would mean it would
		// have been initialized with shared font
		// However, sometimes (when setting a row or column format) an
		// initialized
		// XFRecord may have its font overridden by the column/row

		font = f;
	}
}
